/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package mnstudent.global;

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.ParsePosition;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import javax.swing.text.AbstractDocument.Content;

public class NumericTextField extends JTextField implements
    NumericPlainDocument.InsertErrorListener {
  public NumericTextField() {
    this(null, 0, null);
  }

  public NumericTextField(String text, int columns, DecimalFormat format) {
    super(null, text, columns);

    NumericPlainDocument numericDoc = (NumericPlainDocument) getDocument();
    if (format != null) {
      numericDoc.setFormat(format);
    }

    numericDoc.addInsertErrorListener(this);
  }

  public NumericTextField(int columns, DecimalFormat format) {
    this(null, columns, format);
  }

  public NumericTextField(String text) {
    this(text, 0, null);
  }

  public NumericTextField(String text, int columns) {
    this(text, columns, null);
  }

  public void setFormat(DecimalFormat format) {
    ((NumericPlainDocument) getDocument()).setFormat(format);
  }

  public DecimalFormat getFormat() {
    return ((NumericPlainDocument) getDocument()).getFormat();
  }

  public void formatChanged() {
    // Notify change of format attributes.
    setFormat(getFormat());
  }

  // Methods to get the field value
  public Long getLongValue() throws ParseException {
    return ((NumericPlainDocument) getDocument()).getLongValue();
  }

  public Double getDoubleValue() throws ParseException {
    return ((NumericPlainDocument) getDocument()).getDoubleValue();
  }

  public Number getNumberValue() throws ParseException {
    return ((NumericPlainDocument) getDocument()).getNumberValue();
  }

  // Methods to install numeric values
  public void setValue(Number number) {
    setText(getFormat().format(number));
  }

  public void setValue(long l) {
    setText(getFormat().format(l));
    ;
  }

  public void setValue(double d) {
    setText(getFormat().format(d));
  }

  public void normalize() throws ParseException {
    // format the value according to the format string
    setText(getFormat().format(getNumberValue()));
  }

  // Override to handle insertion error
  public void insertFailed(NumericPlainDocument doc, int offset, String str,
      AttributeSet a) {
    // By default, just beep
    Toolkit.getDefaultToolkit().beep();
  }

  // Method to create default model
  protected Document createDefaultModel() {
    return new NumericPlainDocument();
  }

  // Test code
  public static void main(String[] args) {
    try {
        UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
    } catch (Exception evt) {}
  
    DecimalFormat format = new DecimalFormat("#,###.###");
    format.setGroupingUsed(true);
    format.setGroupingSize(3);
    format.setParseIntegerOnly(false);

    JFrame f = new JFrame("Numeric Text Field Example");
    final NumericTextField tf = new NumericTextField(10, format);

    tf.setValue((double) 123456.789);

    JLabel lbl = new JLabel("Type a number: ");
    f.getContentPane().add(tf, "East");
    f.getContentPane().add(lbl, "West");

    tf.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        try {
          tf.normalize();
          Long l = tf.getLongValue();
          System.out.println("Value is (Long)" + l);
        } catch (ParseException e1) {
          try {
            Double d = tf.getDoubleValue();
            System.out.println("Value is (Double)" + d);
          } catch (ParseException e2) {
            System.out.println(e2);
          }
        }
      }
    });
    f.pack();
    f.setVisible(true);
  }
}

class NumericPlainDocument extends PlainDocument {
  public NumericPlainDocument() {
    setFormat(null);
  }

  public NumericPlainDocument(DecimalFormat format) {
    setFormat(format);
  }

  public NumericPlainDocument(AbstractDocument.Content content,
      DecimalFormat format) {
    super(content);
    setFormat(format);

    try {
      format
          .parseObject(content.getString(0, content.length()),
              parsePos);
    } catch (Exception e) {
      throw new IllegalArgumentException(
          "Initial content not a valid number");
    }

    if (parsePos.getIndex() != content.length() - 1) {
      throw new IllegalArgumentException(
          "Initial content not a valid number");
    }
  }

  public void setFormat(DecimalFormat fmt) {
    this.format = fmt != null ? fmt : (DecimalFormat) defaultFormat.clone();

    decimalSeparator = format.getDecimalFormatSymbols()
        .getDecimalSeparator();
    groupingSeparator = format.getDecimalFormatSymbols()
        .getGroupingSeparator();
    positivePrefix = format.getPositivePrefix();
    positivePrefixLen = positivePrefix.length();
    negativePrefix = format.getNegativePrefix();
    negativePrefixLen = negativePrefix.length();
    positiveSuffix = format.getPositiveSuffix();
    positiveSuffixLen = positiveSuffix.length();
    negativeSuffix = format.getNegativeSuffix();
    negativeSuffixLen = negativeSuffix.length();
  }

  public DecimalFormat getFormat() {
    return format;
  }

  public Number getNumberValue() throws ParseException {
    try {
      String content = getText(0, getLength());
      parsePos.setIndex(0);
      Number result = format.parse(content, parsePos);
      if (parsePos.getIndex() != getLength()) {
        throw new ParseException("Not a valid number: " + content, 0);
      }

      return result;
    } catch (BadLocationException e) {
      throw new ParseException("Not a valid number", 0);
    }
  }

  public Long getLongValue() throws ParseException {
    Number result = getNumberValue();
    if ((result instanceof Long) == false) {
      throw new ParseException("Not a valid long", 0);
    }

    return (Long) result;
  }

  public Double getDoubleValue() throws ParseException {
    Number result = getNumberValue();
    if ((result instanceof Long) == false
        && (result instanceof Double) == false) {
      throw new ParseException("Not a valid double", 0);
    }

    if (result instanceof Long) {
      result = new Double(result.doubleValue());
    }

    return (Double) result;
  }

  public void insertString(int offset, String str, AttributeSet a)
      throws BadLocationException {
    if (str == null || str.length() == 0) {
      return;
    }

    Content content = getContent();
    int length = content.length();
    int originalLength = length;

    parsePos.setIndex(0);

    // Create the result of inserting the new data,
    // but ignore the trailing newline
    String targetString = content.getString(0, offset) + str
        + content.getString(offset, length - offset - 1);

    // Parse the input string and check for errors
    do {
      boolean gotPositive = targetString.startsWith(positivePrefix);
      boolean gotNegative = targetString.startsWith(negativePrefix);

      length = targetString.length();

      // If we have a valid prefix, the parse fails if the
      // suffix is not present and the error is reported
      // at index 0. So, we need to add the appropriate
      // suffix if it is not present at this point.
      if (gotPositive == true || gotNegative == true) {
        String suffix;
        int suffixLength;
        int prefixLength;

        if (gotPositive == true && gotNegative == true) {
          // This happens if one is the leading part of
          // the other - e.g. if one is "(" and the other "(("
          if (positivePrefixLen > negativePrefixLen) {
            gotNegative = false;
          } else {
            gotPositive = false;
          }
        }

        if (gotPositive == true) {
          suffix = positiveSuffix;
          suffixLength = positiveSuffixLen;
          prefixLength = positivePrefixLen;
        } else {
          // Must have the negative prefix
          suffix = negativeSuffix;
          suffixLength = negativeSuffixLen;
          prefixLength = negativePrefixLen;
        }

        // If the string consists of the prefix alone,
        // do nothing, or the result won't parse.
        if (length == prefixLength) {
          break;
        }

        // We can't just add the suffix, because part of it
        // may already be there. For example, suppose the
        // negative prefix is "(" and the negative suffix is
        // "$)". If the user has typed "(345$", then it is not
        // correct to add "$)". Instead, only the missing part
        // should be added, in this case ")".
        if (targetString.endsWith(suffix) == false) {
          int i;
          for (i = suffixLength - 1; i > 0; i--) {
            if (targetString
                .regionMatches(length - i, suffix, 0, i)) {
              targetString += suffix.substring(i);
              break;
            }
          }

          if (i == 0) {
            // None of the suffix was present
            targetString += suffix;
          }

          length = targetString.length();
        }
      }

      format.parse(targetString, parsePos);

      int endIndex = parsePos.getIndex();
      if (endIndex == length) {
        break; // Number is acceptable
      }

      // Parse ended early
      // Since incomplete numbers don't always parse, try
      // to work out what went wrong.
      // First check for an incomplete positive prefix
      if (positivePrefixLen > 0 && endIndex < positivePrefixLen
          && length <= positivePrefixLen
          && targetString.regionMatches(0, positivePrefix, 0, length)) {
        break; // Accept for now
      }

      // Next check for an incomplete negative prefix
      if (negativePrefixLen > 0 && endIndex < negativePrefixLen
          && length <= negativePrefixLen
          && targetString.regionMatches(0, negativePrefix, 0, length)) {
        break; // Accept for now
      }

      // Allow a number that ends with the group
      // or decimal separator, if these are in use
      char lastChar = targetString.charAt(originalLength - 1);
      int decimalIndex = targetString.indexOf(decimalSeparator);
      if (format.isGroupingUsed() && lastChar == groupingSeparator
          && decimalIndex == -1) {
        // Allow a "," but only in integer part
        break;
      }

      if (format.isParseIntegerOnly() == false
          && lastChar == decimalSeparator
          && decimalIndex == originalLength - 1) {
        // Allow a ".", but only one
        break;
      }

      // No more corrections to make: must be an error
      if (errorListener != null) {
        errorListener.insertFailed(this, offset, str, a);
      }
      return;
    } while (true == false);

    // Finally, add to the model
    super.insertString(offset, str, a);
  }

  public void addInsertErrorListener(InsertErrorListener l) {
    if (errorListener == null) {
      errorListener = l;
      return;
    }
    throw new IllegalArgumentException(
        "InsertErrorListener already registered");
  }

  public void removeInsertErrorListener(InsertErrorListener l) {
    if (errorListener == l) {
      errorListener = null;
    }
  }

  public interface InsertErrorListener {
    public abstract void insertFailed(NumericPlainDocument doc, int offset,
        String str, AttributeSet a);
  }

  protected InsertErrorListener errorListener;

  protected DecimalFormat format;

  protected char decimalSeparator;

  protected char groupingSeparator;

  protected String positivePrefix;

  protected String negativePrefix;

  protected int positivePrefixLen;

  protected int negativePrefixLen;

  protected String positiveSuffix;

  protected String negativeSuffix;

  protected int positiveSuffixLen;

  protected int negativeSuffixLen;

  protected ParsePosition parsePos = new ParsePosition(0);

  protected static DecimalFormat defaultFormat = new DecimalFormat();
}
